aspera-cli 4.25.6 → 4.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +74 -47
  4. data/CONTRIBUTING.md +1 -1
  5. data/lib/aspera/api/aoc.rb +118 -78
  6. data/lib/aspera/api/node.rb +101 -49
  7. data/lib/aspera/ascp/installation.rb +94 -30
  8. data/lib/aspera/cli/extended_value.rb +1 -0
  9. data/lib/aspera/cli/formatter.rb +47 -40
  10. data/lib/aspera/cli/manager.rb +30 -4
  11. data/lib/aspera/cli/plugins/aoc.rb +214 -136
  12. data/lib/aspera/cli/plugins/ats.rb +3 -3
  13. data/lib/aspera/cli/plugins/base.rb +17 -42
  14. data/lib/aspera/cli/plugins/config.rb +5 -3
  15. data/lib/aspera/cli/plugins/console.rb +3 -3
  16. data/lib/aspera/cli/plugins/faspex.rb +5 -5
  17. data/lib/aspera/cli/plugins/faspex5.rb +20 -18
  18. data/lib/aspera/cli/plugins/node.rb +66 -70
  19. data/lib/aspera/cli/plugins/oauth.rb +5 -12
  20. data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
  21. data/lib/aspera/cli/plugins/preview.rb +116 -80
  22. data/lib/aspera/cli/plugins/server.rb +2 -10
  23. data/lib/aspera/cli/plugins/shares.rb +7 -7
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/dot_container.rb +7 -3
  26. data/lib/aspera/environment.rb +3 -2
  27. data/lib/aspera/log.rb +1 -1
  28. data/lib/aspera/preview/file_types.rb +1 -1
  29. data/lib/aspera/preview/generator.rb +146 -91
  30. data/lib/aspera/preview/options.rb +4 -1
  31. data/lib/aspera/preview/terminal.rb +50 -20
  32. data/lib/aspera/preview/utils.rb +76 -34
  33. data/lib/aspera/products/transferd.rb +1 -1
  34. data/lib/aspera/rest.rb +1 -0
  35. data/lib/aspera/rest_list.rb +23 -16
  36. data/lib/aspera/secret_hider.rb +3 -1
  37. data/lib/aspera/uri_reader.rb +17 -2
  38. data.tar.gz.sig +0 -0
  39. metadata +5 -5
  40. metadata.gz.sig +0 -0
@@ -56,7 +56,7 @@ module Aspera
56
56
  commands = %i[create list show modify delete node cluster entitlement]
57
57
  command = options.get_next_command(commands)
58
58
  # those do not require access key id
59
- access_key_id = instance_identifier unless %i[create list].include?(command)
59
+ access_key_id = options.instance_identifier unless %i[create list].include?(command)
60
60
  case command
61
61
  when :create
62
62
  params = value_create_modify(command: command, default: {})
@@ -151,7 +151,7 @@ module Aspera
151
151
  if options.get_option(:cloud) || options.get_option(:region)
152
152
  server_data = server_by_cloud_region
153
153
  else
154
- server_id = instance_identifier
154
+ server_id = options.instance_identifier
155
155
  server_data = @ats_api_open.all_servers.find{ |i| i['id'].eql?(server_id)}
156
156
  raise BadIdentifier.new('server', server_id) if server_data.nil?
157
157
  end
@@ -179,7 +179,7 @@ module Aspera
179
179
 
180
180
  def execute_action_api_key
181
181
  command = options.get_next_command(%i[instances create list show delete])
182
- concerned_id = instance_identifier if %i[show delete].include?(command)
182
+ concerned_id = options.instance_identifier if %i[show delete].include?(command)
183
183
  rest_add_header = {}
184
184
  if !command.eql?(:instances)
185
185
  instance = options.get_option(:instance)
@@ -8,28 +8,22 @@ module Aspera
8
8
  module Plugins
9
9
  # Base class for command plugins
10
10
  class Base
11
- # Operations without id (create list)
12
- GLOBAL_OPS = %i[create list].freeze
13
- # Operations with id (modify delete show)
14
- INSTANCE_OPS = %i[modify delete show].freeze
15
- # All standard operations (create list modify delete show)
16
- ALL_OPS = (GLOBAL_OPS + INSTANCE_OPS).freeze
17
-
11
+ module Operations
12
+ # Operations without id: `create` `list`
13
+ GLOBAL = %i[create list].freeze
14
+ # Operations on singleton: `modify` `show`
15
+ SINGLETON = %i[modify show].freeze
16
+ # Operations with id: `modify` `show` `delete`
17
+ INSTANCE = (SINGLETON + %i[delete]).freeze
18
+ # All standard operations: `create` `list` `modify` `show` `delete`
19
+ ALL = (GLOBAL + INSTANCE).freeze
20
+ end
18
21
  class << self
19
22
  def declare_options(options)
20
23
  options.declare(:query, 'Additional filter for for some commands (list/delete)', allowed: [Hash, Array, NilClass])
21
24
  options.declare(:bulk, 'Bulk operation (only some)', allowed: Allowed::TYPES_BOOLEAN, default: false)
22
25
  options.declare(:bfail, 'Bulk operation error handling', allowed: Allowed::TYPES_BOOLEAN, default: true)
23
26
  end
24
-
25
- # @return [Hash,NilClass] `{field:,value:}` if identifier is a percent selector, else `nil`
26
- def percent_selector(identifier)
27
- Aspera.assert_type(identifier, String)
28
- if (m = identifier.match(REGEX_LOOKUP_ID_BY_FIELD))
29
- return {field: m[1], value: ExtendedValue.instance.evaluate(m[2], context: "percent selector: #{m[1]}")}
30
- end
31
- return
32
- end
33
27
  end
34
28
 
35
29
  def initialize(context:)
@@ -62,34 +56,19 @@ module Aspera
62
56
  options.parser.separator('OPTIONS:') if has_options
63
57
  end
64
58
 
65
- # Resource identifier as positional parameter
66
- #
67
- # @param description [String] description of the identifier
68
- # @param &block [Proc] block to search for identifier based on attribute value
69
- # @return [String, Array] identifier or list of ids
70
- def instance_identifier(description: 'identifier')
71
- res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
72
- # Can be an Array
73
- if res_id.is_a?(String) && (m = Base.percent_selector(res_id))
74
- Aspera.assert(block_given?, type: Cli::BadArgument){"Percent syntax for #{description} not supported in this context"}
75
- res_id = yield(m[:field], m[:value])
76
- end
77
- return res_id
78
- end
79
-
80
59
  # For create and delete operations: execute one action or multiple if bulk is yes
81
60
  # @param command [Symbol] Operation: :create, :delete, ...
82
61
  # @param descr [String] Description of the value
83
62
  # @param values [Object] Value(s), or type of value to get from user
84
63
  # @param id_result [String] Key in result hash to use as identifier
85
64
  # @param fields [Array] Fields to display
86
- # @param &block [Proc] Block to execute for each value
87
- def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default)
65
+ # @param block [Proc] Block to execute for each value
66
+ def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default, &block)
88
67
  Aspera.assert(block_given?){'missing block'}
89
68
  is_bulk = options.get_option(:bulk)
90
69
  case values
91
70
  when :identifier
92
- values = instance_identifier(description: descr)
71
+ values = options.instance_identifier(description: descr)
93
72
  when Class
94
73
  values = value_create_modify(command: command, description: descr, type: values, bulk: is_bulk)
95
74
  end
@@ -134,7 +113,7 @@ module Aspera
134
113
  # @param delete_style [String] If set, the delete operation by array in payload
135
114
  # @param id_as_arg [String] If set, the id is provided as url argument ?<id_as_arg>=<id>
136
115
  # @param is_singleton [Boolean] If `true`, entity is the full path to the resource
137
- # @param block [Proc] Block to search for identifier based on attribute value
116
+ # @param &block [Proc] Block to search for identifier based on attribute value
138
117
  # @return [Hash] Result suitable for CLI result
139
118
  def entity_execute(
140
119
  api:,
@@ -148,11 +127,11 @@ module Aspera
148
127
  list_query: nil,
149
128
  &block
150
129
  )
151
- command = options.get_next_command(ALL_OPS) if command.nil?
130
+ command = options.get_next_command(Operations::ALL) if command.nil?
152
131
  if is_singleton
153
132
  one_res_path = entity
154
- elsif INSTANCE_OPS.include?(command)
155
- one_res_id = instance_identifier(&block)
133
+ elsif Operations::INSTANCE.include?(command)
134
+ one_res_id = options.instance_identifier(&block)
156
135
  one_res_path = "#{entity}/#{one_res_id}"
157
136
  one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
158
137
  end
@@ -248,10 +227,6 @@ module Aspera
248
227
  end
249
228
  return value
250
229
  end
251
-
252
- # Percent selector: select by this field for this value
253
- REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
254
- private_constant :REGEX_LOOKUP_ID_BY_FIELD
255
230
  end
256
231
  end
257
232
  end
@@ -568,7 +568,9 @@ module Aspera
568
568
 
569
569
  def install_transfer_sdk
570
570
  asked_version = options.get_next_argument('transferd version', mandatory: false)
571
- name, version, folder = Ascp::Installation.instance.install_sdk(url: options.get_option(:sdk_url, mandatory: true), version: asked_version)
571
+ sdk_url = options.get_option(:sdk_url, mandatory: true)
572
+ sdk_url = nil if sdk_url.eql?(SpecialValues::DEF)
573
+ name, version, folder = Ascp::Installation.instance.retrieve_sdk(url: sdk_url, version: asked_version)
572
574
  return Main.result_status("Installed #{name} version #{version} in #{folder}")
573
575
  end
574
576
 
@@ -641,7 +643,7 @@ module Aspera
641
643
 
642
644
  def execute_preset(action: nil, name: nil)
643
645
  action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
644
- name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
646
+ name = options.instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
645
647
  name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
646
648
  # Those operations require existing option
647
649
  raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
@@ -820,7 +822,7 @@ module Aspera
820
822
  when :list
821
823
  return Main.result_object_list(OAuth::Factory.instance.persisted_tokens)
822
824
  when :show
823
- data = OAuth::Factory.instance.get_token_info(instance_identifier)
825
+ data = OAuth::Factory.instance.get_token_info(options.instance_identifier)
824
826
  raise Cli::Error, 'Unknown identifier' if data.nil?
825
827
  return Main.result_single_object(data)
826
828
  end
@@ -130,15 +130,15 @@ module Aspera
130
130
  fields: %w[id contact name status]
131
131
  )
132
132
  when :show
133
- transfer_id = instance_identifier(description: 'transfer ID')
133
+ transfer_id = options.instance_identifier(description: 'transfer ID')
134
134
  return Main.result_single_object(api_console.read("transfers/#{transfer_id}"))
135
135
  when :files
136
- transfer_id = instance_identifier(description: 'transfer ID')
136
+ transfer_id = options.instance_identifier(description: 'transfer ID')
137
137
  query = query_read_delete(default: {})
138
138
  query['limit'] ||= 100
139
139
  return Main.result_object_list(api_console.read("transfers/#{transfer_id}/files", query))
140
140
  when :start, :pause, :cancel, :resume, :rerun, :change_rate, :change_policy, :move_forwards, :move_back
141
- transfer_id = instance_identifier(description: 'transfer ID')
141
+ transfer_id = options.instance_identifier(description: 'transfer ID')
142
142
  return Main.result_single_object(api_console.update("transfers/#{transfer_id}/#{command}", query_read_delete))
143
143
  end
144
144
  end
@@ -291,7 +291,7 @@ module Aspera
291
291
  command_pkg = options.get_next_command(%i[send receive list show], aliases: {recv: :receive})
292
292
  case command_pkg
293
293
  when :show
294
- delivery_id = instance_identifier
294
+ delivery_id = options.instance_identifier
295
295
  return Main.result_single_object(mailbox_filtered_entries(stop_at_id: delivery_id).find{ |p| p[PACKAGE_MATCH_FIELD].eql?(delivery_id)})
296
296
  when :list
297
297
  return Main.result_object_list(mailbox_filtered_entries, fields: [PACKAGE_MATCH_FIELD, 'title', 'items'])
@@ -307,7 +307,7 @@ module Aspera
307
307
  first_source = delivery_info['sources'].first
308
308
  first_source['paths'].concat(transfer.source_list)
309
309
  source_id = options.get_option(:remote_source)
310
- if source_id && (m = Base.percent_selector(source_id))
310
+ if source_id && (m = Manager.percent_selector(source_id))
311
311
  Aspera.assert(m[:field].eql?('name'), type: Cli::BadArgument){'only name as selector, or give id'}
312
312
  source_list = api_v3.read('source_shares')['items']
313
313
  source_id = self.class.get_source_id_by_name(m[:value], source_list)
@@ -348,7 +348,7 @@ module Aspera
348
348
  )
349
349
  end
350
350
  # get command line parameters
351
- delivery_id = instance_identifier
351
+ delivery_id = options.instance_identifier
352
352
  Aspera.assert(!delivery_id.empty?){'empty id'}
353
353
  recipient = options.get_option(:recipient)
354
354
  if delivery_id.eql?(SpecialValues::ALL)
@@ -441,7 +441,7 @@ module Aspera
441
441
  when :list
442
442
  return Main.result_object_list(source_list)
443
443
  else # :info :node
444
- source_id = instance_identifier do |field, value|
444
+ source_id = options.instance_identifier do |field, value|
445
445
  Aspera.assert(field.eql?('name'), type: Cli::BadArgument){'only name as selector, or give id'}
446
446
  self.class.get_source_id_by_name(value, source_list)
447
447
  end.to_i
@@ -506,7 +506,7 @@ module Aspera
506
506
  return entity_execute(api: api_v4, entity: 'metadata_profiles')
507
507
  when :package
508
508
  pkg_box_type = options.get_next_command([:users])
509
- pkg_box_id = instance_identifier
509
+ pkg_box_id = options.instance_identifier
510
510
  return entity_execute(api: api_v4, entity: "#{pkg_box_type}/#{pkg_box_id}/packages")
511
511
  end
512
512
  when :address_book
@@ -98,6 +98,8 @@ module Aspera
98
98
  options.declare(:shared_folder, 'Send package with files from shared folder')
99
99
  options.declare(:group_type, 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes)
100
100
  options.parse_options!
101
+ # [Aspera::Api::Faspex]
102
+ @api_v5 = nil
101
103
  end
102
104
 
103
105
  # if recipient is just an email, then convert to expected API hash : name and type
@@ -114,7 +116,7 @@ module Aspera
114
116
  parameters[type].map! do |recipient_data|
115
117
  # If just a string, make a general lookup and build expected name/type hash
116
118
  if recipient_data.is_a?(String)
117
- matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
119
+ matched = @api_v5.lookup_with_q('contacts', value: recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
118
120
  recipient_data = {
119
121
  name: matched['name'],
120
122
  recipient_type: matched['type']
@@ -297,7 +299,7 @@ module Aspera
297
299
  return Main.result_transfer(transfer.start(transfer_spec))
298
300
  else
299
301
  # send from remote shared folder
300
- if (m = Base.percent_selector(shared_folder))
302
+ if (m = Manager.percent_selector(shared_folder))
301
303
  shared_folder = @api_v5.lookup_entity_by_field(
302
304
  entity: 'shared_folders',
303
305
  field: m[:field],
@@ -376,7 +378,7 @@ module Aspera
376
378
  command = options.get_next_command(%i[show browse status delete receive send list])
377
379
  package_id =
378
380
  if %i[receive show browse status delete].include?(command)
379
- @api_v5.pub_link_context&.key?('package_id') ? @api_v5.pub_link_context['package_id'] : instance_identifier
381
+ @api_v5.pub_link_context&.key?('package_id') ? @api_v5.pub_link_context['package_id'] : options.instance_identifier
380
382
  end
381
383
  case command
382
384
  when :show
@@ -419,7 +421,7 @@ module Aspera
419
421
  entity: res_sym.to_s
420
422
  }
421
423
  res_id_query = :default
422
- available_commands = ALL_OPS
424
+ available_commands = Operations::ALL
423
425
  case res_sym
424
426
  when :metadata_profiles
425
427
  exec_args[:entity] = 'configuration/metadata_profiles'
@@ -437,7 +439,7 @@ module Aspera
437
439
  available_commands += [:reset_password]
438
440
  when :oauth_clients
439
441
  exec_args[:display_fields] = Formatter.all_but('public_key')
440
- exec_args[:api] = Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.args_from_options(options))
442
+ exec_args[:api] = Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.kwargs_from_options(options))
441
443
  exec_args[:list_query] = {'expand': true, 'no_api_path': true, 'client_types[]': 'public'}
442
444
  when :shared_inboxes, :workgroups
443
445
  available_commands += %i[members saml_groups invite_external_collaborator]
@@ -459,13 +461,13 @@ module Aspera
459
461
  return Main.result_object_list(data, total: total, fields: exec_args[:display_fields])
460
462
  when :shared_folders
461
463
  # nodes
462
- node_id = instance_identifier do |field, value|
464
+ node_id = options.instance_identifier do |field, value|
463
465
  @api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
464
466
  end
465
467
  shfld_entity = "nodes/#{node_id}/shared_folders"
466
- sh_command = options.get_next_command(ALL_OPS + [:user])
468
+ sh_command = options.get_next_command(Operations::ALL + [:user])
467
469
  case sh_command
468
- when *ALL_OPS
470
+ when *Operations::ALL
469
471
  return entity_execute(
470
472
  api: @api_v5,
471
473
  entity: shfld_entity,
@@ -474,7 +476,7 @@ module Aspera
474
476
  @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
475
477
  end
476
478
  when :user
477
- sh_id = instance_identifier do |field, value|
479
+ sh_id = options.instance_identifier do |field, value|
478
480
  @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
479
481
  end
480
482
  user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
@@ -485,13 +487,13 @@ module Aspera
485
487
  end
486
488
  when :browse
487
489
  # nodes
488
- node_id = instance_identifier do |field, value|
490
+ node_id = options.instance_identifier do |field, value|
489
491
  @api_v5.lookup_entity_by_field(entity: 'nodes', value: value, field: field)['id']
490
492
  end
491
493
  return browse_folder("nodes/#{node_id}/browse")
492
494
  when :invite_external_collaborator
493
495
  # :shared_inboxes, :workgroups
494
- shared_inbox_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
496
+ shared_inbox_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
495
497
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
496
498
  creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
497
499
  result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
@@ -505,7 +507,7 @@ module Aspera
505
507
  return Main.result_single_object(result)
506
508
  when :members, :saml_groups
507
509
  # res_command := :shared_inboxes, :workgroups
508
- res_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
510
+ res_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
509
511
  res_path = "#{res_sym}/#{res_id}/#{res_command}"
510
512
  list_key = res_command.to_s
511
513
  list_key = 'groups' if res_command.eql?(:saml_groups)
@@ -515,7 +517,7 @@ module Aspera
515
517
  users = options.get_next_argument('user id, %name:, or Array')
516
518
  users = [users] unless users.is_a?(Array)
517
519
  users = users.map do |user|
518
- if (m = Base.percent_selector(user))
520
+ if (m = Manager.percent_selector(user))
519
521
  @api_v5.lookup_entity_by_field(
520
522
  entity: 'accounts',
521
523
  field: m[:field],
@@ -546,7 +548,7 @@ module Aspera
546
548
  end
547
549
  when :reset_password
548
550
  # :accounts
549
- contact_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
551
+ contact_id = options.instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
550
552
  @api_v5.create("accounts/#{contact_id}/reset_password", {})
551
553
  return Main.result_status('password reset, user shall check email')
552
554
  end
@@ -617,7 +619,7 @@ module Aspera
617
619
  command = options.get_next_command(ACTIONS)
618
620
  unless %i{postprocessing health}.include?(command)
619
621
  # create an API object with the same options, but with a different subpath
620
- @api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
622
+ @api_v5 = Api::Faspex.new(**Oauth.kwargs_from_options(options))
621
623
  # in case user wants to use HTTPGW tell transfer agent how to get address
622
624
  transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
623
625
  end
@@ -660,7 +662,7 @@ module Aspera
660
662
  when :list
661
663
  return Main.result_object_list(all_shared_folders)
662
664
  when :browse
663
- shared_folder_id = instance_identifier do |field, value|
665
+ shared_folder_id = options.instance_identifier do |field, value|
664
666
  matches = all_shared_folders.select{ |i| i[field].eql?(value)}
665
667
  raise "no match for #{field} = #{value}" if matches.empty?
666
668
  raise "multiple matches for #{field} = #{value}" if matches.length > 1
@@ -674,7 +676,7 @@ module Aspera
674
676
  return execute_admin
675
677
  when :invitations
676
678
  invitation_endpoint = 'invitations'
677
- invitation_command = options.get_next_command(%i[resend].concat(ALL_OPS))
679
+ invitation_command = options.get_next_command(%i[resend].concat(Operations::ALL))
678
680
  case invitation_command
679
681
  when :create
680
682
  return do_bulk_operation(command: invitation_command, descr: 'data') do |params|
@@ -682,7 +684,7 @@ module Aspera
682
684
  @api_v5.create(invitation_endpoint, params)
683
685
  end
684
686
  when :resend
685
- @api_v5.create("#{invitation_endpoint}/#{instance_identifier}/resend", nil)
687
+ @api_v5.create("#{invitation_endpoint}/#{options.instance_identifier}/resend", nil)
686
688
  return Main.result_status('Invitation resent')
687
689
  else
688
690
  return entity_execute(