aspera-cli 4.15.0 → 4.17.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 (108) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +375 -280
  5. data/CONTRIBUTING.md +71 -18
  6. data/README.md +1978 -1656
  7. data/bin/ascli +13 -31
  8. data/bin/asession +32 -22
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/agent/alpha.rb +117 -0
  11. data/lib/aspera/agent/base.rb +61 -0
  12. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  13. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +116 -116
  14. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +21 -19
  15. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +21 -33
  16. data/lib/aspera/agent/trsdk.rb +188 -0
  17. data/lib/aspera/api/aoc.rb +586 -0
  18. data/lib/aspera/api/ats.rb +46 -0
  19. data/lib/aspera/api/cos_node.rb +95 -0
  20. data/lib/aspera/api/node.rb +344 -0
  21. data/lib/aspera/ascmd.rb +47 -14
  22. data/lib/aspera/{fasp → ascp}/installation.rb +54 -15
  23. data/lib/aspera/{fasp → ascp}/management.rb +14 -14
  24. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  25. data/lib/aspera/assert.rb +45 -0
  26. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  27. data/lib/aspera/cli/extended_value.rb +5 -5
  28. data/lib/aspera/cli/formatter.rb +27 -14
  29. data/lib/aspera/cli/hints.rb +7 -6
  30. data/lib/aspera/cli/main.rb +49 -29
  31. data/lib/aspera/cli/manager.rb +46 -36
  32. data/lib/aspera/cli/plugin.rb +34 -20
  33. data/lib/aspera/cli/plugin_factory.rb +61 -0
  34. data/lib/aspera/cli/plugins/alee.rb +7 -7
  35. data/lib/aspera/cli/plugins/aoc.rb +168 -132
  36. data/lib/aspera/cli/plugins/ats.rb +33 -33
  37. data/lib/aspera/cli/plugins/bss.rb +3 -4
  38. data/lib/aspera/cli/plugins/config.rb +250 -272
  39. data/lib/aspera/cli/plugins/console.rb +8 -6
  40. data/lib/aspera/cli/plugins/cos.rb +20 -19
  41. data/lib/aspera/cli/plugins/faspex.rb +71 -60
  42. data/lib/aspera/cli/plugins/faspex5.rb +212 -133
  43. data/lib/aspera/cli/plugins/node.rb +83 -75
  44. data/lib/aspera/cli/plugins/orchestrator.rb +36 -44
  45. data/lib/aspera/cli/plugins/preview.rb +33 -31
  46. data/lib/aspera/cli/plugins/server.rb +33 -32
  47. data/lib/aspera/cli/plugins/shares.rb +39 -33
  48. data/lib/aspera/cli/sync_actions.rb +9 -9
  49. data/lib/aspera/cli/transfer_agent.rb +45 -25
  50. data/lib/aspera/cli/transfer_progress.rb +2 -3
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/colors.rb +5 -0
  53. data/lib/aspera/command_line_builder.rb +16 -14
  54. data/lib/aspera/coverage.rb +21 -0
  55. data/lib/aspera/data_repository.rb +33 -2
  56. data/lib/aspera/environment.rb +5 -4
  57. data/lib/aspera/faspex_gw.rb +13 -11
  58. data/lib/aspera/faspex_postproc.rb +6 -5
  59. data/lib/aspera/id_generator.rb +4 -2
  60. data/lib/aspera/json_rpc.rb +10 -8
  61. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  62. data/lib/aspera/keychain/macos_security.rb +29 -22
  63. data/lib/aspera/log.rb +5 -4
  64. data/lib/aspera/nagios.rb +7 -2
  65. data/lib/aspera/node_simulator.rb +213 -0
  66. data/lib/aspera/oauth/base.rb +143 -0
  67. data/lib/aspera/oauth/factory.rb +124 -0
  68. data/lib/aspera/oauth/generic.rb +34 -0
  69. data/lib/aspera/oauth/jwt.rb +51 -0
  70. data/lib/aspera/oauth/url_json.rb +31 -0
  71. data/lib/aspera/oauth/web.rb +50 -0
  72. data/lib/aspera/oauth.rb +5 -328
  73. data/lib/aspera/open_application.rb +7 -7
  74. data/lib/aspera/persistency_action_once.rb +13 -14
  75. data/lib/aspera/persistency_folder.rb +3 -2
  76. data/lib/aspera/preview/file_types.rb +53 -267
  77. data/lib/aspera/preview/generator.rb +7 -5
  78. data/lib/aspera/preview/terminal.rb +17 -7
  79. data/lib/aspera/preview/utils.rb +8 -7
  80. data/lib/aspera/proxy_auto_config.rb +6 -3
  81. data/lib/aspera/rest.rb +187 -140
  82. data/lib/aspera/rest_error_analyzer.rb +1 -0
  83. data/lib/aspera/rest_errors_aspera.rb +5 -3
  84. data/lib/aspera/resumer.rb +77 -0
  85. data/lib/aspera/secret_hider.rb +5 -2
  86. data/lib/aspera/ssh.rb +15 -8
  87. data/lib/aspera/temp_file_manager.rb +1 -1
  88. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  89. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  90. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  91. data/lib/aspera/{fasp → transfer}/parameters.rb +95 -120
  92. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +23 -19
  93. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  94. data/lib/aspera/transfer/sync.rb +273 -0
  95. data/lib/aspera/{fasp → transfer}/uri.rb +10 -9
  96. data/lib/aspera/web_server_simple.rb +12 -3
  97. data.tar.gz.sig +0 -0
  98. metadata +92 -68
  99. metadata.gz.sig +0 -0
  100. data/lib/aspera/aoc.rb +0 -606
  101. data/lib/aspera/ats_api.rb +0 -47
  102. data/lib/aspera/cos_node.rb +0 -93
  103. data/lib/aspera/fasp/agent_aspera.rb +0 -126
  104. data/lib/aspera/fasp/agent_base.rb +0 -48
  105. data/lib/aspera/fasp/agent_trsdk.rb +0 -146
  106. data/lib/aspera/fasp/resume_policy.rb +0 -77
  107. data/lib/aspera/node.rb +0 -338
  108. data/lib/aspera/sync.rb +0 -219
@@ -4,23 +4,26 @@ require 'aspera/cli/plugins/node'
4
4
  require 'aspera/cli/plugins/ats'
5
5
  require 'aspera/cli/basic_auth_plugin'
6
6
  require 'aspera/cli/transfer_agent'
7
- require 'aspera/fasp/agent_node'
8
- require 'aspera/fasp/transfer_spec'
9
- require 'aspera/aoc'
10
- require 'aspera/node'
7
+ require 'aspera/agent/node'
8
+ require 'aspera/transfer/spec'
9
+ require 'aspera/api/aoc'
10
+ require 'aspera/api/node'
11
11
  require 'aspera/persistency_action_once'
12
12
  require 'aspera/id_generator'
13
+ require 'aspera/assert'
13
14
  require 'securerandom'
14
15
  require 'date'
15
16
 
16
17
  module Aspera
17
18
  module Cli
18
19
  module Plugins
19
- class Aoc < Aspera::Cli::BasicAuthPlugin
20
+ class Aoc < Cli::BasicAuthPlugin
20
21
  AOC_PATH_API_CLIENTS = 'admin/api-clients'
21
22
  # default redirect for AoC web auth
22
- DEFAULT_REDIRECT = 'http://localhost:12345'
23
- private_constant :AOC_PATH_API_CLIENTS, :DEFAULT_REDIRECT
23
+ REDIRECT_LOCALHOST = 'http://localhost:12345'
24
+ # OAuth methods supported
25
+ STD_AUTH_TYPES = %i[web jwt].freeze
26
+ private_constant :AOC_PATH_API_CLIENTS, :REDIRECT_LOCALHOST, :STD_AUTH_TYPES
24
27
  class << self
25
28
  def application_name
26
29
  'Aspera on Cloud'
@@ -30,13 +33,13 @@ module Aspera
30
33
  # no protocol ?
31
34
  base_url = "https://#{base_url}" unless base_url.match?(%r{^[a-z]{1,6}://})
32
35
  # only org provided ?
33
- base_url = "#{base_url}.#{Aspera::AoC::PROD_DOMAIN}" unless base_url.include?('.')
36
+ base_url = "#{base_url}.#{Api::AoC::PROD_DOMAIN}" unless base_url.include?('.')
34
37
  # AoC is only https
35
38
  return nil unless base_url.start_with?('https://')
36
- result = Rest.new({base_url: base_url, redirect_max: 10}).read('')
39
+ result = Rest.new(base_url: base_url, redirect_max: 10).read('')
37
40
  # Any AoC is on this domain
38
- return nil unless result[:http].uri.host.end_with?(Aspera::AoC::PROD_DOMAIN)
39
- Log.log.debug{'AoC Main page: #{result[:http].body.include?(Aspera::AoC::PRODUCT_NAME)}'}
41
+ return nil unless result[:http].uri.host.end_with?(Api::AoC::PROD_DOMAIN)
42
+ Log.log.debug{"AoC Main page: #{result[:http].body.include?(Api::AoC::PRODUCT_NAME)}"}
40
43
  base_url = result[:http].uri.to_s if result[:http].uri.path.include?('/public')
41
44
  # either in standard domain, or product name in page
42
45
  return {
@@ -47,7 +50,7 @@ module Aspera
47
50
 
48
51
  def private_key_required?(url)
49
52
  # pub link do not need private key
50
- return AoC.link_info(url)[:token].nil?
53
+ return Api::AoC.link_info(url)[:token].nil?
51
54
  end
52
55
 
53
56
  # @param [Hash] env : options, formatter
@@ -60,9 +63,9 @@ module Aspera
60
63
  options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: true)
61
64
  options.parse_options!
62
65
  instance_url = options.get_option(:url, mandatory: true)
63
- pub_link_info = AoC.link_info(instance_url)
66
+ pub_link_info = Api::AoC.link_info(instance_url)
64
67
  if !pub_link_info[:token].nil?
65
- pub_api = Rest.new({base_url: "https://#{URI.parse(pub_link_info[:url]).host}/api/v1"})
68
+ pub_api = Rest.new(base_url: "https://#{URI.parse(pub_link_info[:url]).host}/api/v1")
66
69
  pub_info = pub_api.read('env/url_token_check', {token: pub_link_info[:token]})[:data]
67
70
  preset_value = {
68
71
  link: instance_url
@@ -99,7 +102,7 @@ module Aspera
99
102
  formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
100
103
  formatter.display_status('Check or create in integration:')
101
104
  formatter.display_status("- name: #{@info[:name]}")
102
- formatter.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
105
+ formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
103
106
  formatter.display_status('- origin: localhost')
104
107
  formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
105
108
  end
@@ -112,13 +115,14 @@ module Aspera
112
115
  formatter.display_status('We will use web authentication to bootstrap.')
113
116
  auto_set_pub_key = true
114
117
  auto_set_jwt = true
115
- aoc_api.oauth.generic_parameters[:grant_method] = :web
116
- aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
117
- aoc_api.oauth.specific_parameters[:redirect_uri] = DEFAULT_REDIRECT
118
+ raise 'TODO'
119
+ # aoc_api.oauth.grant_method = :web
120
+ # aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
121
+ # aoc_api.oauth.specific_parameters[:redirect_uri] = REDIRECT_LOCALHOST
118
122
  end
119
123
  myself = object.aoc_api.read('self')[:data]
120
124
  if auto_set_pub_key
121
- raise Cli::Error, 'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
125
+ Aspera.assert(myself['public_key'].empty?, exception_class: Cli::Error){'Public key is already set in profile (use --override=yes)'} unless option_override
122
126
  formatter.display_status('Updating profile with the public key.')
123
127
  aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
124
128
  end
@@ -165,21 +169,25 @@ module Aspera
165
169
  client_registration_token
166
170
  client_access_key
167
171
  kms_profile].freeze
168
- PACKAGE_QUERY_DEFAULT = {'archived' => false, 'exclude_dropbox_packages' => true, 'has_content' => true, 'received' => true}.freeze
172
+ PACKAGE_RECEIVED_BASE_QUERY = {
173
+ 'archived' => false,
174
+ 'has_content' => true,
175
+ 'received' => true,
176
+ 'completed' => true}.freeze
169
177
 
170
- def initialize(env)
171
- super(env)
178
+ def initialize(**env)
179
+ super
172
180
  @cache_workspace_info = nil
173
181
  @cache_home_node_file = nil
174
182
  @cache_api_aoc = nil
175
- options.declare(:auth, 'OAuth type of authentication', values: Oauth::STD_AUTH_TYPES, default: :jwt)
183
+ options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
176
184
  options.declare(:client_id, 'OAuth API client identifier')
177
185
  options.declare(:client_secret, 'OAuth API client secret')
178
- options.declare(:scope, 'OAuth scope for AoC API calls', default: AoC::SCOPE_FILES_USER)
186
+ options.declare(:scope, 'OAuth scope for AoC API calls', default: Api::AoC::SCOPE_FILES_USER)
179
187
  options.declare(:redirect_uri, 'OAuth API client redirect URI')
180
188
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
181
189
  options.declare(:passphrase, 'RSA private key passphrase')
182
- options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Aspera::AoC::DEFAULT_WORKSPACE)
190
+ options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
183
191
  options.declare(:new_user_option, 'New user creation option for unknown package recipients')
184
192
  options.declare(:validate_metadata, 'Validate shared inbox metadata', values: :bool, default: true)
185
193
  options.parse_options!
@@ -190,9 +198,9 @@ module Aspera
190
198
  OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
191
199
 
192
200
  def api_from_options(new_base_path)
193
- create_values = {subpath: new_base_path, secret_finder: @agents[:config]}
201
+ create_values = {subpath: new_base_path, secret_finder: config}
194
202
  # create an API object with the same options, but with a different subpath
195
- return Aspera::AoC.new(**OPTIONS_NEW.each_with_object(create_values) { |i, m|m[i] = options.get_option(i) unless options.get_option(i).nil?})
203
+ return Api::AoC.new(**OPTIONS_NEW.each_with_object(create_values) { |i, m|m[i] = options.get_option(i) unless options.get_option(i).nil?})
196
204
  rescue ArgumentError => e
197
205
  if (m = e.message.match(/missing keyword: :(.*)$/))
198
206
  raise Cli::Error, "Missing option: #{m[1]}"
@@ -201,7 +209,14 @@ module Aspera
201
209
  end
202
210
 
203
211
  def aoc_api
204
- @cache_api_aoc = api_from_options(AoC::API_V1) if @cache_api_aoc.nil?
212
+ if @cache_api_aoc.nil?
213
+ @cache_api_aoc = api_from_options(Api::AoC::API_V1)
214
+ organization = @cache_api_aoc.read('organization')[:data]
215
+ if organization['http_gateway_enabled'] && organization['http_gateway_server_url']
216
+ transfer.httpgw_url_cb = lambda { organization['http_gateway_server_url'] }
217
+ # @cache_api_aoc.current_user_info['connect_disabled']
218
+ end
219
+ end
205
220
  return @cache_api_aoc
206
221
  end
207
222
 
@@ -209,7 +224,7 @@ module Aspera
209
224
  # @return identifier
210
225
  def get_resource_id_from_args(resource_class_path)
211
226
  return instance_identifier do |field, value|
212
- raise Cli::BadArgument, 'only selection by name is supported' unless field.eql?('name')
227
+ Aspera.assert(field.eql?('name'), exception_class: Cli::BadArgument){'only selection by name is supported'}
213
228
  aoc_api.lookup_by_name(resource_class_path, value)['id']
214
229
  end
215
230
  end
@@ -218,11 +233,11 @@ module Aspera
218
233
  return "#{resource_class_path}/#{get_resource_id_from_args(resource_class_path)}"
219
234
  end
220
235
 
221
- # Call aoc_api.read with same parameters.
222
- # Use paging if necessary to get all results
223
- # @return [Hash] {list: , total: }
224
- def read_with_paging(resource_class_path, base_query)
225
- raise 'Query must be Hash' unless base_query.is_a?(Hash)
236
+ # Call block with same query using paging and response information
237
+ # @return [Hash] {data: , total: }
238
+ def api_call_paging(base_query={})
239
+ Aspera.assert_type(base_query, Hash){'query'}
240
+ Aspera.assert(block_given?)
226
241
  # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
227
242
  base_query['per_page'] = 1000 unless base_query.key?('per_page')
228
243
  max_items = base_query.delete(MAX_ITEMS)
@@ -235,7 +250,7 @@ module Aspera
235
250
  loop do
236
251
  query = base_query.clone
237
252
  query['page'] = current_page
238
- result = aoc_api.read(resource_class_path, query)
253
+ result = yield(query)
239
254
  total_count = result[:http]['X-Total-Count']
240
255
  page_count += 1
241
256
  current_page += 1
@@ -247,7 +262,38 @@ module Aspera
247
262
  break if !max_pages.nil? && page_count >= max_pages
248
263
  end
249
264
  item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
250
- return {list: item_list, total: total_count}
265
+ return {data: item_list, total: total_count}
266
+ end
267
+
268
+ # read using the query and paging
269
+ # @return [Hash] {data: , total: }
270
+ def api_read_all(resource_class_path, base_query={})
271
+ return api_call_paging(base_query) do |query|
272
+ aoc_api.read(resource_class_path, query)
273
+ end
274
+ end
275
+
276
+ # list all entities, given additional, default and user's queries
277
+ def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
278
+ Aspera.assert_type(base_query, Hash)
279
+ Aspera.assert_type(default_query, Hash)
280
+ user_query = query_read_delete(default: default_query)
281
+ # caller may add specific modifications or checks
282
+ yield(user_query) if block_given?
283
+ return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query)))
284
+ end
285
+
286
+ def resolve_dropbox_name_default_ws_id(query)
287
+ if query.key?('dropbox_name')
288
+ # convenience: specify name instead of id
289
+ raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
290
+ # TODO : craft a query that looks for dropbox only in current workspace
291
+ query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
292
+ query.delete('dropbox_name')
293
+ end
294
+ query['workspace_id'] ||= aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
295
+ # by default show dropbox packages only for dropboxes
296
+ query['exclude_dropbox_packages'] = !query.key?('dropbox_id') unless query.key?('exclude_dropbox_packages')
251
297
  end
252
298
 
253
299
  NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
@@ -263,7 +309,7 @@ module Aspera
263
309
  scope: scope
264
310
  )
265
311
  file_id = top_node_api.read("access_keys/#{top_node_api.app_info[:node_info]['access_key']}")[:data]['root_file_id'] if file_id.nil?
266
- node_plugin = Node.new(@agents, api: top_node_api)
312
+ node_plugin = Node.new(**init_params, api: top_node_api)
267
313
  case command_repo
268
314
  when *Node::COMMANDS_GEN4
269
315
  return node_plugin.execute_command_gen4(command_repo, file_id)
@@ -275,20 +321,20 @@ module Aspera
275
321
  source_folder = options.get_next_argument('folder of source files', type: String)
276
322
  case push_pull
277
323
  when :push
278
- client_direction = Fasp::TransferSpec::DIRECTION_SEND
324
+ client_direction = Transfer::Spec::DIRECTION_SEND
279
325
  client_folder = source_folder
280
326
  server_folder = transfer.destination_folder(client_direction)
281
327
  when :pull
282
- client_direction = Fasp::TransferSpec::DIRECTION_RECEIVE
328
+ client_direction = Transfer::Spec::DIRECTION_RECEIVE
283
329
  client_folder = transfer.destination_folder(client_direction)
284
330
  server_folder = source_folder
285
- else raise 'internal error'
331
+ else Aspera.error_unreachable_line
286
332
  end
287
333
  client_apfid = top_node_api.resolve_api_fid(file_id, client_folder)
288
334
  server_apfid = top_node_api.resolve_api_fid(file_id, server_folder)
289
335
  # force node as transfer agent
290
- @agents[:transfer].agent_instance = Fasp::AgentNode.new({
291
- url: client_apfid[:api].params[:base_url],
336
+ transfer.agent_instance = Agent::Node.new({
337
+ url: client_apfid[:api].base_url,
292
338
  username: client_apfid[:api].app_info[:node_info]['access_key'],
293
339
  password: client_apfid[:api].oauth_token,
294
340
  root_id: client_apfid[:file_id]
@@ -303,22 +349,21 @@ module Aspera
303
349
  server_apfid[:file_id],
304
350
  client_direction,
305
351
  add_ts)))
306
- else raise "INTERNAL ERROR: Missing case: #{command_repo}"
352
+ else Aspera.error_unreachable_line
307
353
  end # command_repo
308
- # raise 'internal error:shall not reach here'
354
+ Aspera.error_unreachable_line
309
355
  end # execute_nodegen4_command
310
356
 
311
357
  def execute_admin_action
312
358
  # upgrade scope to admin
313
- aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
359
+ aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
314
360
  command_admin = options.get_next_command(%i[ats resource usage_reports analytics subscription auth_providers])
315
361
  case command_admin
316
362
  when :auth_providers
317
363
  command_auth_prov = options.get_next_command(%i[list update])
318
364
  case command_auth_prov
319
365
  when :list
320
- providers = aoc_api.read('admin/auth_providers')[:data]
321
- return {type: :object_list, data: providers}
366
+ return result_list('admin/auth_providers')
322
367
  when :update
323
368
  raise 'not implemented'
324
369
  end
@@ -378,15 +423,15 @@ module Aspera
378
423
  result = bss_api.create('graphql', {'variables' => {'organization_id' => org['id']}, 'query' => graphql_query})[:data]['data']
379
424
  return {type: :single_object, data: result['aoc']['bssSubscription']}
380
425
  when :ats
381
- ats_api = Rest.new(aoc_api.params.deep_merge({
382
- base_url: "#{aoc_api.params[:base_url]}/admin/ats/pub/v1",
383
- auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
426
+ ats_api = Rest.new(**aoc_api.params.deep_merge({
427
+ base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
428
+ auth: {scope: Api::AoC::SCOPE_FILES_ADMIN_USER}
384
429
  }))
385
- return Ats.new(@agents).execute_action_gen(ats_api)
430
+ return Ats.new(**init_params).execute_action_gen(ats_api)
386
431
  when :analytics
387
- analytics_api = Rest.new(aoc_api.params.deep_merge({
388
- base_url: "#{aoc_api.params[:base_url].gsub('/api/v1', '')}/analytics/v2",
389
- auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
432
+ analytics_api = Rest.new(**aoc_api.params.deep_merge({
433
+ base_url: "#{aoc_api.base_url.gsub('/api/v1', '')}/analytics/v2",
434
+ auth: {scope: Api::AoC::SCOPE_FILES_ADMIN_USER}
390
435
  }))
391
436
  command_analytics = options.get_next_command(%i[application_events transfers])
392
437
  case command_analytics
@@ -402,18 +447,22 @@ module Aspera
402
447
  when :organizations then aoc_api.current_user_info['organization_id']
403
448
  when :users then aoc_api.current_user_info['id']
404
449
  when :nodes then aoc_api.current_user_info['id'] # TODO: consistent ? # rubocop:disable Lint/DuplicateBranch
405
- else raise 'Internal error'
450
+ else Aspera.error_unreachable_line
406
451
  end
407
452
  filter = options.get_option(:query) || {}
408
453
  filter['limit'] ||= 100
409
454
  if options.get_option(:once_only, mandatory: true)
410
455
  saved_date = []
411
456
  start_date_persistency = PersistencyActionOnce.new(
412
- manager: @agents[:persistency],
457
+ manager: persistency,
413
458
  data: saved_date,
414
- ids: IdGenerator.from_list(['aoc_ana_date', options.get_option(:url, mandatory: true), aoc_api.context[:workspace_name]].push(
459
+ id: IdGenerator.from_list([
460
+ 'aoc_ana_date',
461
+ options.get_option(:url, mandatory: true),
462
+ aoc_api.context(:files)[:workspace_name],
415
463
  filter_resource.to_s,
416
- filter_id)))
464
+ filter_id
465
+ ]))
417
466
  start_date_time = saved_date.first
418
467
  stop_date_time = Time.now.utc.strftime('%FT%T.%LZ')
419
468
  # Log.log().error("start: #{start_date_time}")
@@ -484,9 +533,7 @@ module Aspera
484
533
  when :group_membership then default_fields.push(*%w[group_id member_type member_id])
485
534
  when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
486
535
  end
487
- items = read_with_paging(resource_class_path, query_read_delete(default: default_query))
488
- formatter.display_item_count(items[:list].length, items[:total])
489
- return {type: :object_list, data: items[:list], fields: default_fields}
536
+ return result_list(resource_class_path, fields: default_fields, default_query: default_query)
490
537
  when :show
491
538
  object = aoc_api.read(resource_instance_path)[:data]
492
539
  # default: show all, but certificate
@@ -511,12 +558,13 @@ module Aspera
511
558
  return Main.result_success
512
559
  when :do
513
560
  command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
514
- aoc_api.context(:none)
561
+ # init context
562
+ aoc_api.context(:files)
515
563
  return execute_nodegen4_command(command_repo, res_id)
516
- else raise 'unknown command'
564
+ else Aspera.error_unexpected_value(command)
517
565
  end
518
566
  when :usage_reports
519
- return {type: :object_list, data: aoc_api.read('usage_reports', {workspace_id: aoc_api.context(:none)[:workspace_id]})[:data]}
567
+ return result_list('usage_reports', base_query: {workspace_id: aoc_api.context(:files)[:workspace_id]})
520
568
  end
521
569
  end
522
570
 
@@ -538,10 +586,10 @@ module Aspera
538
586
  when :reminder
539
587
  # send an email reminder with list of orgs
540
588
  user_email = options.get_option(:username, mandatory: true)
541
- Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").create('organization_reminders', {email: user_email})[:data]
589
+ Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").create('organization_reminders', {email: user_email})[:data]
542
590
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
543
591
  when :servers
544
- return {type: :object_list, data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
592
+ return {type: :object_list, data: Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers')[:data]}
545
593
  when :bearer_token
546
594
  return {type: :text, data: aoc_api.oauth_token}
547
595
  when :organization
@@ -549,15 +597,15 @@ module Aspera
549
597
  when :tier_restrictions
550
598
  return { type: :single_object, data: aoc_api.read('tier_restrictions')[:data] }
551
599
  when :user
552
- case options.get_next_command(%i[workspaces profile])
600
+ case options.get_next_command(%i[workspaces profile preferences])
553
601
  # when :settings
554
602
  # return {type: :object_list,data: aoc_api.read('client_settings/')[:data]}
555
603
  when :workspaces
556
604
  case options.get_next_command(%i[list current])
557
605
  when :list
558
- return {type: :object_list, data: aoc_api.read('workspaces')[:data], fields: %w[id name]}
606
+ return result_list('workspaces', fields: %w[id name])
559
607
  when :current
560
- return { type: :single_object, data: aoc_api.read("workspaces/#{aoc_api.context(:none)[:workspace_id]}")[:data] }
608
+ return { type: :single_object, data: aoc_api.read("workspaces/#{aoc_api.context(:files)[:workspace_id]}")[:data] }
561
609
  end
562
610
  when :profile
563
611
  case options.get_next_command(%i[show modify])
@@ -567,19 +615,25 @@ module Aspera
567
615
  aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties', type: Hash))
568
616
  return Main.result_status('modified')
569
617
  end
618
+ when :preferences
619
+ user_preferences_res = "users/#{aoc_api.current_user_info(exception: true)['id']}/user_interaction_preferences"
620
+ case options.get_next_command(%i[show modify])
621
+ when :show
622
+ return { type: :single_object, data: aoc_api.read(user_preferences_res)[:data] }
623
+ when :modify
624
+ aoc_api.update(user_preferences_res, options.get_next_argument('properties', type: Hash))
625
+ return Main.result_status('modified')
626
+ end
570
627
  end
571
628
  when :packages
572
- package_command = options.get_next_command(%i[shared_inboxes send recv list show delete].concat(Node::NODE4_READ_ACTIONS))
629
+ package_command = options.get_next_command(%i[shared_inboxes send receive list show delete].concat(Node::NODE4_READ_ACTIONS), aliases: {recv: :receive})
573
630
  case package_command
574
631
  when :shared_inboxes
575
632
  case options.get_next_command(%i[list show])
576
633
  when :list
577
- query = query_read_delete
578
- if query.nil?
579
- query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
580
- query['workspace_id'] = aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
581
- end
582
- return {type: :object_list, data: aoc_api.read('dropbox_memberships', query)[:data], fields: ['dropbox_id', 'dropbox.name']}
634
+ default_query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
635
+ default_query['workspace_id'] = aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
636
+ return result_list('dropbox_memberships', fields: %w[dropbox_id dropbox.name], default_query: default_query)
583
637
  when :show
584
638
  return {type: :single_object, data: aoc_api.read(get_resource_path_from_args('dropboxes'), query)[:data]}
585
639
  end
@@ -603,7 +657,7 @@ module Aspera
603
657
  Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
604
658
  # return all info on package (especially package id)
605
659
  return { type: :single_object, data: created_package[:info]}
606
- when :recv
660
+ when :receive
607
661
  ids_to_download = nil
608
662
  if !aoc_api.public_link.nil?
609
663
  aoc_api.assert_public_link_types(['view_received_package'])
@@ -615,8 +669,9 @@ module Aspera
615
669
  skip_ids_data = []
616
670
  skip_ids_persistency = nil
617
671
  if options.get_option(:once_only, mandatory: true)
672
+ # TODO: add query info to id
618
673
  skip_ids_persistency = PersistencyActionOnce.new(
619
- manager: @agents[:persistency],
674
+ manager: persistency,
620
675
  data: skip_ids_data,
621
676
  id: IdGenerator.from_list(
622
677
  ['aoc_recv',
@@ -624,30 +679,30 @@ module Aspera
624
679
  aoc_api.context[:workspace_id]
625
680
  ].concat(aoc_api.additional_persistence_ids)))
626
681
  end
627
- if ExtendedValue::ALL.eql?(ids_to_download)
628
- query = query_read_delete(default: PACKAGE_QUERY_DEFAULT)
629
- raise 'option query must be Hash' unless query.is_a?(Hash)
630
- if query.key?('dropbox_name')
631
- # convenience: specify name instead of id
632
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
633
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
634
- query.delete('dropbox_name')
635
- end
636
- query['workspace_id'] ||= aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
637
- # get list of packages in inbox
638
- package_info = aoc_api.read('packages', query)[:data]
682
+ case ids_to_download
683
+ when ExtendedValue::ALL, ExtendedValue::INIT
684
+ query = query_read_delete(default: PACKAGE_RECEIVED_BASE_QUERY)
685
+ Aspera.assert_type(query, Hash){'query'}
686
+ resolve_dropbox_name_default_ws_id(query)
639
687
  # remove from list the ones already downloaded
640
- ids_to_download = package_info.map{|e|e['id']}
688
+ all_ids = api_read_all('packages', query)[:data].map{|e|e['id']}
689
+ if ids_to_download.eql?(ExtendedValue::INIT)
690
+ Aspera.assert(skip_ids_persistency){'Only with option once_only'}
691
+ skip_ids_persistency.data.clear.concat(all_ids)
692
+ skip_ids_persistency.save
693
+ return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
694
+ end
641
695
  # array here
642
- ids_to_download.reject!{|id|skip_ids_data.include?(id)}
696
+ ids_to_download = all_ids.reject{|id|skip_ids_data.include?(id)}
697
+ else
698
+ ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
643
699
  end # ExtendedValue::ALL
644
700
  # list here
645
- ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
646
701
  result_transfer = []
647
702
  formatter.display_status("found #{ids_to_download.length} package(s).")
648
703
  ids_to_download.each do |package_id|
649
704
  package_info = aoc_api.read("packages/#{package_id}")[:data]
650
- formatter.display_status("downloading package: #{package_info['name']}")
705
+ formatter.display_status("downloading package: [#{package_info['id']}] #{package_info['name']}")
651
706
  package_node_api = aoc_api.node_api_from(
652
707
  node_id: package_info['node_id'],
653
708
  workspace_id: aoc_api.context[:workspace_id],
@@ -656,7 +711,7 @@ module Aspera
656
711
  statuses = transfer.start(
657
712
  package_node_api.transfer_spec_gen4(
658
713
  package_info['contents_file_id'],
659
- Fasp::TransferSpec::DIRECTION_RECEIVE,
714
+ Transfer::Spec::DIRECTION_RECEIVE,
660
715
  {'paths'=> [{'source' => '.'}]}),
661
716
  rest_token: package_node_api)
662
717
  result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
@@ -673,36 +728,25 @@ module Aspera
673
728
  return { type: :single_object, data: package_info }
674
729
  when :list
675
730
  display_fields = %w[id name bytes_transferred]
676
- query = query_read_delete(default: PACKAGE_QUERY_DEFAULT)
677
- raise 'option query must be Hash' unless query.is_a?(Hash)
678
- if query.key?('dropbox_name')
679
- # convenience: specify name instead of id
680
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
681
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
682
- query.delete('dropbox_name')
683
- end
684
- if aoc_api.context[:workspace_id].eql?(:undefined)
685
- display_fields.push('workspace_id')
686
- else
687
- query['workspace_id'] ||= aoc_api.context[:workspace_id]
688
- end
689
- packages = aoc_api.read('packages', query)[:data]
690
- return {type: :object_list, data: packages, fields: display_fields}
731
+ display_fields.push('workspace_id') if aoc_api.context[:workspace_id].eql?(:undefined)
732
+ return result_list('packages', fields: display_fields, base_query: PACKAGE_RECEIVED_BASE_QUERY) do |query|
733
+ resolve_dropbox_name_default_ws_id(query)
734
+ end
691
735
  when :delete
692
736
  return do_bulk_operation(command: package_command, descr: 'identifier', values: identifier) do |id|
693
- raise 'expecting String identifier' unless id.is_a?(String) || id.is_a?(Integer)
737
+ Aspera.assert_values(id.class, [String, Integer]){'identifier'}
694
738
  aoc_api.delete("packages/#{id}")[:data]
695
739
  end
696
740
  when *Node::NODE4_READ_ACTIONS
697
741
  package_id = instance_identifier
698
742
  package_info = aoc_api.read("packages/#{package_id}")[:data]
699
- return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: Aspera::Node::SCOPE_USER)
743
+ return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: Api::Node::SCOPE_USER)
700
744
  end
701
745
  when :files
702
746
  command_repo = options.get_next_command([:short_link].concat(NODE4_EXT_COMMANDS))
703
747
  case command_repo
704
748
  when *NODE4_EXT_COMMANDS
705
- return execute_nodegen4_command(command_repo, aoc_api.context[:home_node_id], file_id: aoc_api.context[:home_file_id], scope: Aspera::Node::SCOPE_USER)
749
+ return execute_nodegen4_command(command_repo, aoc_api.context[:home_node_id], file_id: aoc_api.context[:home_file_id], scope: Api::Node::SCOPE_USER)
706
750
  when :short_link
707
751
  link_type = options.get_next_argument('link type', expected: %i[public private])
708
752
  short_link_command = options.get_next_command(%i[create delete list])
@@ -720,7 +764,7 @@ module Aspera
720
764
  purpose = case link_type
721
765
  when :public then 'token_auth_redirection'
722
766
  when :private then 'shared_folder_auth_link'
723
- else raise 'internal error'
767
+ else Aspera.error_unreachable_line
724
768
  end
725
769
  case short_link_command
726
770
  when :delete
@@ -754,8 +798,7 @@ module Aspera
754
798
  # embed: 'updated_by_user',
755
799
  sort: '-created_at'
756
800
  }
757
- result = aoc_api.read('short_links', list_params)[:data]
758
- return {type: :object_list, data: result, fields: Formatter.all_but('data')}
801
+ return result_list('short_links', fields: Formatter.all_but('data'), base_query: list_params)
759
802
  when :create
760
803
  creation_params = {
761
804
  purpose: purpose,
@@ -781,7 +824,7 @@ module Aspera
781
824
  if link_type.eql?(:public)
782
825
  # TODO: merge with node permissions ?
783
826
  # TODO: access level as arg
784
- access_levels = Aspera::Node::ACCESS_LEVELS # ['delete','list','mkdir','preview','read','rename','write']
827
+ access_levels = Api::Node::ACCESS_LEVELS # ['delete','list','mkdir','preview','read','rename','write']
785
828
  folder_name = File.basename(folder_dest)
786
829
  perm_data = {
787
830
  'file_id' => shared_apfid[:file_id],
@@ -810,9 +853,7 @@ module Aspera
810
853
  when :automation
811
854
  Log.log.warn('BETA: work under progress')
812
855
  # automation api is not in the same place
813
- automation_rest_params = aoc_api.params.clone
814
- automation_rest_params[:base_url].gsub!('/api/', '/automation/')
815
- automation_api = Rest.new(automation_rest_params)
856
+ automation_api = Rest.new(**aoc_api.params.merge(base_url: aoc_api.base_url.gsub('/api/', '/automation/')))
816
857
  command_automation = options.get_next_command(%i[workflows instances])
817
858
  case command_automation
818
859
  when :instances
@@ -846,21 +887,16 @@ module Aspera
846
887
  url = value_create_modify(command: command, type: String)
847
888
  uri = URI.parse(url)
848
889
  server = WebServerSimple.new(uri)
849
- server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.context(:none)[:workspace_id])
850
- trap('INT') { server.shutdown }
851
- formatter.display_status("Faspex 4 gateway listening on #{url}")
852
- Log.log.info("Listening on #{url}")
853
- # this is blocking until server exits
890
+ server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.context(:files)[:workspace_id])
854
891
  server.start
855
892
  return Main.result_status('Gateway terminated')
856
- else
857
- raise "internal error: #{command}"
893
+ else Aspera.error_unreachable_line
858
894
  end # action
859
- raise 'internal error: command shall return'
895
+ Aspera.error_unreachable_line
860
896
  end
861
897
 
862
898
  private :execute_admin_action
863
- end # AoC
864
- end # Plugins
865
- end # Cli
866
- end # Aspera
899
+ end
900
+ end
901
+ end
902
+ end