aspera-cli 4.21.1 → 4.22.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 (105) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +52 -22
  5. data/CONTRIBUTING.md +69 -148
  6. data/README.md +929 -668
  7. data/bin/ascli +5 -14
  8. data/bin/asession +1 -3
  9. data/examples/get_proto_file.rb +4 -3
  10. data/examples/proxy.pac +20 -20
  11. data/lib/aspera/agent/base.rb +11 -5
  12. data/lib/aspera/agent/connect.rb +30 -28
  13. data/lib/aspera/agent/{alpha.rb → desktop.rb} +35 -31
  14. data/lib/aspera/agent/direct.rb +141 -121
  15. data/lib/aspera/agent/httpgw.rb +22 -26
  16. data/lib/aspera/agent/node.rb +14 -11
  17. data/lib/aspera/agent/transferd.rb +30 -19
  18. data/lib/aspera/api/alee.rb +1 -1
  19. data/lib/aspera/api/aoc.rb +6 -6
  20. data/lib/aspera/api/cos_node.rb +2 -2
  21. data/lib/aspera/api/httpgw.rb +7 -3
  22. data/lib/aspera/api/node.rb +10 -8
  23. data/lib/aspera/ascmd.rb +3 -3
  24. data/lib/aspera/ascp/installation.rb +53 -72
  25. data/lib/aspera/ascp/management.rb +1 -1
  26. data/lib/aspera/assert.rb +11 -2
  27. data/lib/aspera/cli/error.rb +2 -2
  28. data/lib/aspera/cli/extended_value.rb +46 -21
  29. data/lib/aspera/cli/formatter.rb +55 -48
  30. data/lib/aspera/cli/hints.rb +1 -1
  31. data/lib/aspera/cli/info.rb +1 -0
  32. data/lib/aspera/cli/main.rb +192 -170
  33. data/lib/aspera/cli/manager.rb +18 -18
  34. data/lib/aspera/cli/plugin.rb +23 -20
  35. data/lib/aspera/cli/plugin_factory.rb +1 -1
  36. data/lib/aspera/cli/plugins/alee.rb +1 -1
  37. data/lib/aspera/cli/plugins/aoc.rb +247 -159
  38. data/lib/aspera/cli/plugins/ats.rb +19 -17
  39. data/lib/aspera/cli/plugins/config.rb +76 -113
  40. data/lib/aspera/cli/plugins/console.rb +5 -3
  41. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  42. data/lib/aspera/cli/plugins/faspex5.rb +111 -84
  43. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  44. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  45. data/lib/aspera/cli/plugins/node.rb +312 -182
  46. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  47. data/lib/aspera/cli/plugins/preview.rb +3 -3
  48. data/lib/aspera/cli/plugins/server.rb +6 -6
  49. data/lib/aspera/cli/plugins/shares.rb +5 -5
  50. data/lib/aspera/cli/sync_actions.rb +19 -18
  51. data/lib/aspera/cli/transfer_agent.rb +5 -5
  52. data/lib/aspera/cli/transfer_progress.rb +2 -2
  53. data/lib/aspera/cli/version.rb +1 -1
  54. data/lib/aspera/command_line_builder.rb +116 -95
  55. data/lib/aspera/coverage.rb +8 -5
  56. data/lib/aspera/environment.rb +26 -17
  57. data/lib/aspera/faspex_gw.rb +14 -14
  58. data/lib/aspera/faspex_postproc.rb +10 -11
  59. data/lib/aspera/hash_ext.rb +4 -14
  60. data/lib/aspera/json_rpc.rb +1 -1
  61. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  62. data/lib/aspera/keychain/factory.rb +41 -0
  63. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  64. data/lib/aspera/keychain/macos_security.rb +19 -11
  65. data/lib/aspera/log.rb +28 -34
  66. data/lib/aspera/nagios.rb +6 -6
  67. data/lib/aspera/node_simulator.rb +8 -8
  68. data/lib/aspera/oauth/base.rb +14 -7
  69. data/lib/aspera/oauth/factory.rb +5 -6
  70. data/lib/aspera/oauth/url_json.rb +6 -6
  71. data/lib/aspera/persistency_action_once.rb +6 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +13 -10
  74. data/lib/aspera/preview/options.rb +16 -16
  75. data/lib/aspera/preview/terminal.rb +4 -4
  76. data/lib/aspera/preview/utils.rb +15 -17
  77. data/lib/aspera/products/connect.rb +35 -1
  78. data/lib/aspera/products/{alpha.rb → desktop.rb} +3 -3
  79. data/lib/aspera/products/transferd.rb +9 -2
  80. data/lib/aspera/proxy_auto_config.rb +2 -2
  81. data/lib/aspera/rest.rb +56 -47
  82. data/lib/aspera/rest_errors_aspera.rb +1 -1
  83. data/lib/aspera/secret_hider.rb +12 -5
  84. data/lib/aspera/ssh.rb +4 -4
  85. data/lib/aspera/temp_file_manager.rb +5 -4
  86. data/lib/aspera/transfer/convert.rb +29 -0
  87. data/lib/aspera/transfer/error_info.rb +66 -66
  88. data/lib/aspera/transfer/parameters.rb +13 -68
  89. data/lib/aspera/transfer/spec.rb +5 -6
  90. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  91. data/lib/aspera/transfer/spec_doc.rb +62 -0
  92. data/lib/aspera/transfer/sync.rb +23 -72
  93. data/lib/aspera/transfer/sync_instance.schema.yaml +13 -0
  94. data/lib/aspera/transfer/sync_session.schema.yaml +79 -0
  95. data/lib/aspera/transfer/uri.rb +6 -6
  96. data/lib/aspera/uri_reader.rb +18 -1
  97. data/lib/aspera/web_auth.rb +1 -1
  98. data/lib/aspera/web_server_simple.rb +53 -44
  99. data.tar.gz.sig +0 -0
  100. metadata +28 -165
  101. metadata.gz.sig +0 -0
  102. data/examples/build_exec +0 -74
  103. data/examples/build_exec_rubyc +0 -40
  104. data/examples/build_package.sh +0 -28
  105. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -53,7 +53,9 @@ module Aspera
53
53
  'completed' => true}.freeze
54
54
  # options and parameters for Api::AoC.new
55
55
  OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
56
- private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW
56
+ PACKAGE_LIST_DEFAULT_FIELDS = %w[id name created_at files_completed bytes_transferred].freeze
57
+
58
+ private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW, :PACKAGE_LIST_DEFAULT_FIELDS
57
59
  class << self
58
60
  def application_name
59
61
  'Aspera on Cloud'
@@ -78,15 +80,16 @@ module Aspera
78
80
  }
79
81
  end
80
82
 
81
- # @param [String] url : url to check
83
+ # @param url [String] url to check
82
84
  # @return [Bool] true if private key is required for the url (i.e. no passcode)
83
85
  def private_key_required?(url)
84
86
  # pub link do not need private key
85
87
  return Api::AoC.link_info(url)[:token].nil?
86
88
  end
87
89
 
88
- # @param [Hash] env : options, formatter
89
- # @param [Hash] params : plugin_sym, instance_url
90
+ # @param object [Plugin] An instance of this class
91
+ # @param private_key_path [String] path to private key
92
+ # @param pub_key_pem [String] PEM of public key
90
93
  # @return [Hash] :preset_value, :test_args
91
94
  def wizard(object:, private_key_path: nil, pub_key_pem: nil)
92
95
  # set vars to look like object
@@ -121,7 +124,7 @@ module Aspera
121
124
  formatter.display_status('Please Login to your Aspera on Cloud instance.')
122
125
  formatter.display_status('Navigate to: 👤 → Account Settings → Profile → Public Key')
123
126
  formatter.display_status('Check or update the value to:'.red.blink)
124
- formatter.display_status(pub_key_pem)
127
+ formatter.display_status(pub_key_pem, hide_secrets: false)
125
128
  if !options.get_option(:test_mode)
126
129
  formatter.display_status('Once updated or validated, press enter.')
127
130
  Environment.instance.open_uri(instance_url)
@@ -203,7 +206,7 @@ module Aspera
203
206
  def api_from_options(new_base_path)
204
207
  create_values = {subpath: new_base_path, secret_finder: config}
205
208
  # create an API object with the same options, but with a different subpath
206
- 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?})
209
+ 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?})
207
210
  rescue ArgumentError => e
208
211
  if (m = e.message.match(/missing keyword: :(.*)$/))
209
212
  raise Cli::Error, "Missing option: #{m[1]}"
@@ -216,13 +219,31 @@ module Aspera
216
219
  @cache_api_aoc = api_from_options(Api::AoC::API_V1)
217
220
  organization = @cache_api_aoc.read('organization')
218
221
  if organization['http_gateway_enabled'] && organization['http_gateway_server_url']
219
- transfer.httpgw_url_cb = lambda { organization['http_gateway_server_url'] }
222
+ transfer.httpgw_url_cb = lambda{organization['http_gateway_server_url']}
220
223
  # @cache_api_aoc.current_user_info['connect_disabled']
221
224
  end
222
225
  end
223
226
  return @cache_api_aoc
224
227
  end
225
228
 
229
+ # Generate or update Hash with workspace id and name (option), if not already set
230
+ # @param hash [Hash, Nil] set in provided hash
231
+ # @param string [Bool] true to set key as string, else as symbol
232
+ # @param name [Bool] include name
233
+ # @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
234
+ def workspace_id_hash(hash: nil, string: false, name: false)
235
+ info = aoc_api.workspace
236
+ hash = {} if hash.nil?
237
+ fields = %i[id]
238
+ fields.push(:name) if name
239
+ fields.each do |i|
240
+ k = "workspace_#{i}"
241
+ k = k.to_sym unless string
242
+ hash[k] = info[i] unless info[i].nil? || hash.key?(k)
243
+ end
244
+ return hash
245
+ end
246
+
226
247
  # Get resource identifier from command line, either directly or from name.
227
248
  # @param resource_class_path url path for resource
228
249
  # @return identifier
@@ -240,7 +261,7 @@ module Aspera
240
261
 
241
262
  # Call block with same query using paging and response information
242
263
  # block must return a hash with :data and :http keys
243
- # @return [Hash] {data: , total: }
264
+ # @return [Hash] {items: , total: }
244
265
  def api_call_paging(base_query={})
245
266
  Aspera.assert_type(base_query, Hash){'query'}
246
267
  Aspera.assert(block_given?)
@@ -268,46 +289,59 @@ module Aspera
268
289
  item_list += add_items
269
290
  break if !max_items.nil? && item_list.count >= max_items
270
291
  break if !max_pages.nil? && page_count >= max_pages
292
+ formatter.long_operation_running("#{item_list.count} / #{total_count}") unless total_count.eql?(item_list.count.to_s)
271
293
  end
294
+ formatter.long_operation_terminated
272
295
  item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
273
- return {data: item_list, total: total_count}
296
+ return {items: item_list, total: total_count}
274
297
  end
275
298
 
276
299
  # read using the query and paging
277
300
  # @return [Hash] {data: , total: }
278
301
  def api_read_all(resource_class_path, base_query={})
279
302
  return api_call_paging(base_query) do |query|
280
- aoc_api.call(operation: 'GET', subpath: resource_class_path, headers: {'Accept' => 'application/json'}, query: query)
303
+ aoc_api.call(operation: 'GET', subpath: resource_class_path, headers: {'Accept' => Rest::MIME_JSON}, query: query)
281
304
  end
282
305
  end
283
306
 
284
- # list all entities, given additional, default and user's queries
307
+ # List all entities, given additional, default and user's queries
285
308
  # @param resource_class_path path to query on API
286
309
  # @param fields fields to display
287
310
  # @param base_query a query applied always
288
311
  # @param default_query default query unless overriden by user
312
+ # @param &block (Optional) calls block with user's or default query
289
313
  def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
290
314
  Aspera.assert_type(base_query, Hash)
291
315
  Aspera.assert_type(default_query, Hash)
292
- user_query = query_read_delete(default: default_query)
293
- # caller may add specific modifications or checks
294
- yield(user_query) if block_given?
295
- return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query).compact))
316
+ query = query_read_delete(default: default_query)
317
+ # caller may add specific modifications or checks to query
318
+ yield(query) if block_given?
319
+ result = api_read_all(resource_class_path, base_query.merge(query).compact)
320
+ return Main.result_object_list(result[:items], fields: fields, total: result[:total])
296
321
  end
297
322
 
323
+ # Translates `dropbox_name` to `dropbox_id` and fills current workspace_id
298
324
  def resolve_dropbox_name_default_ws_id(query)
299
325
  if query.key?('dropbox_name')
300
326
  # convenience: specify name instead of id
301
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
327
+ raise 'Use field dropbox_name or dropbox_id, not both' if query.key?('dropbox_id')
302
328
  # TODO : craft a query that looks for dropbox only in current workspace
303
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
304
- query.delete('dropbox_name')
329
+ query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query.delete('dropbox_name'))['id']
305
330
  end
306
- query['workspace_id'] ||= aoc_api.workspace[:id] unless aoc_api.workspace[:id].eql?(:undefined)
331
+ workspace_id_hash(hash: query, string: true)
307
332
  # by default show dropbox packages only for dropboxes
308
333
  query['exclude_dropbox_packages'] = !query.key?('dropbox_id') unless query.key?('exclude_dropbox_packages')
309
334
  end
310
335
 
336
+ # @return [Hash] {items,total} with all packages according to combination of user's query and default query
337
+ def list_all_packages_with_query
338
+ query = query_read_delete(default: {})
339
+ Aspera.assert_type(query, Hash){'query'}
340
+ PACKAGE_RECEIVED_BASE_QUERY.each{ |k, v| query[k] = v unless query.key?(k)}
341
+ resolve_dropbox_name_default_ws_id(query)
342
+ return api_read_all('packages', query.compact)
343
+ end
344
+
311
345
  NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
312
346
  private_constant :NODE4_EXT_COMMANDS
313
347
 
@@ -316,9 +350,8 @@ module Aspera
316
350
  def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
317
351
  top_node_api = aoc_api.node_api_from(
318
352
  node_id: node_id,
319
- workspace_id: aoc_api.workspace[:id],
320
- workspace_name: aoc_api.workspace[:name],
321
- scope: scope
353
+ scope: scope,
354
+ **workspace_id_hash(name: true)
322
355
  )
323
356
  file_id = top_node_api.read("access_keys/#{top_node_api.app_info[:node_info]['access_key']}")['root_file_id'] if file_id.nil?
324
357
  node_plugin = Node.new(**init_params, api: top_node_api)
@@ -348,7 +381,7 @@ module Aspera
348
381
  transfer.agent_instance = Agent::Node.new(
349
382
  url: client_apfid[:api].base_url,
350
383
  username: client_apfid[:api].app_info[:node_info]['access_key'],
351
- password: client_apfid[:api].oauth.token,
384
+ password: client_apfid[:api].oauth.authorization,
352
385
  root_id: client_apfid[:file_id]
353
386
  )
354
387
  # additional node to node TS info
@@ -422,8 +455,8 @@ module Aspera
422
455
  when :show
423
456
  object = aoc_api.read(resource_instance_path)
424
457
  # default: show all, but certificate
425
- fields = object.keys.reject{|k|k.eql?('certificate')}
426
- return { type: :single_object, data: object, fields: fields }
458
+ fields = object.keys.reject{ |k| k.eql?('certificate')}
459
+ return Main.result_single_object(object, fields: fields)
427
460
  when :modify
428
461
  changes = options.get_next_argument('properties', validation: Hash)
429
462
  return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
@@ -473,60 +506,111 @@ module Aspera
473
506
  when :subscription
474
507
  org = aoc_api.read('organization')
475
508
  bss_graphql = api_from_options('bss/platform/graphql')
476
- # cspell:disable
477
- graphql_query = <<-GRAPHQL
478
- query ($organization_id: ID!) {
479
- aoc (organization_id: $organization_id) {
480
- bssSubscription {
481
- aocVersion
482
- endDate
483
- startDate
484
- termMonths
485
- plan
486
- trial
487
- termType
488
- aocOrganizations {
489
- id
490
- }
491
- additionalStorageVolumeGb
492
- additionalEgressVolumeGb
493
- term {
494
- startDate
495
- endDate
496
- transferVolumeGb
497
- egressVolumeGb
498
- storageVolumeGb
499
- transferVolumeOffsetGb
500
- }
501
- paygoRate {
502
- transferRate
503
- storageRate
504
- currency
505
- }
506
- aocPlanData {
507
- tier
508
- trial
509
- workspaces { max }
510
- users {
511
- planAmount
512
- max
509
+ command_subscription = options.get_next_command(%i[account usage])
510
+ case command_subscription
511
+ when :account
512
+ # cspell:disable
513
+ graphql_query = <<-GRAPHQL
514
+ query ($organization_id: ID!) {
515
+ aoc (organization_id: $organization_id) {
516
+ bssSubscription {
517
+ aocVersion
518
+ endDate
519
+ startDate
520
+ termMonths
521
+ plan
522
+ trial
523
+ termType
524
+ aocOrganizations {
525
+ id
526
+ }
527
+ additionalStorageVolumeGb
528
+ additionalEgressVolumeGb
529
+ term {
530
+ startDate
531
+ endDate
532
+ transferVolumeGb
533
+ egressVolumeGb
534
+ storageVolumeGb
535
+ transferVolumeOffsetGb
536
+ }
537
+ paygoRate {
538
+ transferRate
539
+ storageRate
540
+ currency
541
+ }
542
+ aocPlanData {
543
+ tier
544
+ trial
545
+ workspaces { max }
546
+ users {
547
+ planAmount
548
+ max
549
+ }
550
+ samlIntegration
551
+ activity
552
+ sharedInboxes
553
+ uniqueUrls
554
+ support
555
+ watermarking
556
+ byok
557
+ automation { planAmount, max }
558
+ }
559
+ }
560
+ }
513
561
  }
514
- samlIntegration
515
- activity
516
- sharedInboxes
517
- uniqueUrls
518
- support
519
- watermarking
520
- byok
521
- automation { planAmount, max }
522
- }
523
- }
524
- }
525
- }
526
- GRAPHQL
527
- # cspell:enable
528
- result = bss_graphql.create(nil, {query: graphql_query, variables: {organization_id: org['id']}})['data']
529
- return {type: :single_object, data: result['aoc']['bssSubscription']}
562
+ GRAPHQL
563
+ # cspell:enable
564
+ result = bss_graphql.create(nil, {query: graphql_query, variables: {organization_id: org['id']}})['data']
565
+ return Main.result_single_object(result['aoc']['bssSubscription'])
566
+ when :usage
567
+ # cspell:disable
568
+ graphql_query = <<-GRAPHQL
569
+ query ($organization_id: ID!, $startDate: Date!, $endDate: Date!, $aggregate: TransferUsageAggregateOption!) {
570
+ aoc (organization_id: $organization_id) {
571
+ bssSubscription {
572
+ aocOrganizations { id }
573
+ additionalStorageVolumeGb
574
+ additionalEgressVolumeGb
575
+ aocPlanData {
576
+ tier
577
+ trial
578
+ }
579
+ term {
580
+ transferVolumeGb
581
+ egressVolumeGb
582
+ storageVolumeGb
583
+ startDate
584
+ endDate
585
+ transferVolumeOffsetGb
586
+ }
587
+ termMonths
588
+ transferUsages (startDate: $startDate, endDate: $endDate, aggregate: $aggregate) { mbTotal }
589
+ egressUsages (startDate: $startDate, endDate: $endDate, aggregate: $aggregate) { usageMb }
590
+ }
591
+ subscriptionEntitlements {
592
+ id
593
+ transferUsages (startDate: $startDate, endDate: $endDate, aggregate: $aggregate) { mbTotal }
594
+ egressUsages (startDate: $startDate, endDate: $endDate, aggregate: $aggregate) { usageMb }
595
+ }
596
+ }
597
+ }
598
+ GRAPHQL
599
+ aggregate = options.get_next_argument('aggregation', accept_list: %i[ALL MONTHLY], default: :ALL)
600
+ today = Date.today
601
+ start_date = options.get_next_argument('start date', mandatory: false, default: today.prev_year.strftime('%Y-%m-%d'))
602
+ end_date = options.get_next_argument('end date', mandatory: false, default: today.strftime('%Y-%m-%d'))
603
+ # cspell:enable
604
+ result = bss_graphql.create(
605
+ nil,
606
+ {query: graphql_query,
607
+ variables: {
608
+ organization_id: org['id'],
609
+ aggregate: aggregate,
610
+ startDate: start_date,
611
+ endDate: end_date}})['data']
612
+ return Main.result_single_object(result['aoc'])
613
+ end
530
614
  when :ats
531
615
  ats_api = Rest.new(**aoc_api.params.deep_merge({
532
616
  base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
@@ -543,7 +627,7 @@ module Aspera
543
627
  when :application_events
544
628
  event_type = command_analytics.to_s
545
629
  events = analytics_api.read("organizations/#{aoc_api.current_user_info['organization_id']}/#{event_type}")[event_type]
546
- return {type: :object_list, data: events}
630
+ return Main.result_object_list(events)
547
631
  when :transfers
548
632
  event_type = command_analytics.to_s
549
633
  filter_resource = options.get_next_argument('resource', accept_list: %i[organizations users nodes])
@@ -584,25 +668,26 @@ module Aspera
584
668
  config.send_email_template(values: {ev: tr_event})
585
669
  end
586
670
  end
587
- return {type: :object_list, data: events}
671
+ return Main.result_object_list(events)
588
672
  end
589
673
  when :usage_reports
590
674
  aoc_api.context = :files
591
- return result_list('usage_reports', base_query: {workspace_id: aoc_api.workspace[:id]})
675
+ return result_list('usage_reports', base_query: workspace_id_hash)
592
676
  end
593
677
  end
594
678
 
595
679
  # Create a shared link for the given entity
596
- # @param shared_data [Hash] information for shared data
597
- # @param block [Proc] Optional: called on creation
598
- def short_link_command(shared_data, purpose_public:)
680
+ # @param purpose_public [Symbol]
681
+ # @param shared_data [Hash] information for shared data
682
+ # @param block [Proc] Optional: called on creation
683
+ def short_link_command(purpose_public:, **shared_data)
599
684
  link_type = options.get_next_argument('link type', accept_list: %i[public private])
600
685
  purpose_local = case link_type
601
686
  when :public
602
687
  case purpose_public
603
688
  when /package/ then 'send_package_to_dropbox'
604
689
  when /shared/ then 'token_auth_redirection'
605
- else raise 'error'
690
+ else Aspera.error_unexpected_value(purpose_public){'public link purpose'}
606
691
  end
607
692
  when :private then 'shared_folder_auth_link'
608
693
  else Aspera.error_unreachable_line
@@ -631,7 +716,7 @@ module Aspera
631
716
  result_create_short_link = aoc_api.create('short_links', creation_params)
632
717
  # public: Creation: permission on node
633
718
  yield(result_create_short_link['resource_id']) if block_given? && link_type.eql?(:public)
634
- return {type: :single_object, data: result_create_short_link}
719
+ return Main.result_single_object(result_create_short_link)
635
720
  when :list
636
721
  query = if link_type.eql?(:private)
637
722
  shared_data
@@ -667,6 +752,26 @@ module Aspera
667
752
  end
668
753
  end
669
754
 
755
+ # @return persistency object if option `once_only` is used.
756
+ def package_persistency
757
+ return nil unless options.get_option(:once_only, mandatory: true)
758
+ # TODO: add query info to id
759
+ PersistencyActionOnce.new(
760
+ manager: persistency,
761
+ data: [],
762
+ id: IdGenerator.from_list(
763
+ ['aoc_recv',
764
+ options.get_option(:url, mandatory: true),
765
+ aoc_api.workspace[:id]
766
+ ].concat(aoc_api.additional_persistence_ids)))
767
+ end
768
+
769
+ def reject_packages_from_persistency(all_packages, skip_ids_persistency)
770
+ return if skip_ids_persistency.nil?
771
+ skip_package = skip_ids_persistency.data.each_with_object({}){ |i, m| m[i] = true}
772
+ all_packages.reject!{ |pkg| skip_package[pkg['id']]}
773
+ end
774
+
670
775
  # must be public
671
776
  ACTIONS = %i[reminder servers bearer_token organization tier_restrictions user packages files admin automation gateway].freeze
672
777
 
@@ -688,29 +793,29 @@ module Aspera
688
793
  Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").create('organization_reminders', {email: user_email})
689
794
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
690
795
  when :servers
691
- return {type: :object_list, data: Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers')}
796
+ return Main.result_object_list(Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers'))
692
797
  when :bearer_token
693
- return {type: :text, data: aoc_api.oauth.token}
798
+ return Main.result_text(aoc_api.oauth.authorization)
694
799
  when :organization
695
- return { type: :single_object, data: aoc_api.read('organization') }
800
+ return Main.result_single_object(aoc_api.read('organization'))
696
801
  when :tier_restrictions
697
- return { type: :single_object, data: aoc_api.read('tier_restrictions') }
802
+ return Main.result_single_object(aoc_api.read('tier_restrictions'))
698
803
  when :user
699
804
  case options.get_next_command(%i[workspaces profile preferences])
700
805
  # when :settings
701
- # return {type: :object_list,data: aoc_api.read('client_settings/')}
806
+ # return Main.result_object_list(aoc_api.read('client_settings/'))
702
807
  when :workspaces
703
808
  case options.get_next_command(%i[list current])
704
809
  when :list
705
810
  return result_list('workspaces', fields: %w[id name])
706
811
  when :current
707
812
  aoc_api.context = :files
708
- return { type: :single_object, data: aoc_api.read("workspaces/#{aoc_api.workspace[:id]}") }
813
+ return Main.result_single_object(aoc_api.workspace)
709
814
  end
710
815
  when :profile
711
816
  case options.get_next_command(%i[show modify])
712
817
  when :show
713
- return { type: :single_object, data: aoc_api.current_user_info(exception: true) }
818
+ return Main.result_single_object(aoc_api.current_user_info(exception: true))
714
819
  when :modify
715
820
  aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties', validation: Hash))
716
821
  return Main.result_status('modified')
@@ -719,7 +824,7 @@ module Aspera
719
824
  user_preferences_res = "users/#{aoc_api.current_user_info(exception: true)['id']}/user_interaction_preferences"
720
825
  case options.get_next_command(%i[show modify])
721
826
  when :show
722
- return { type: :single_object, data: aoc_api.read(user_preferences_res) }
827
+ return Main.result_single_object(aoc_api.read(user_preferences_res))
723
828
  when :modify
724
829
  aoc_api.update(user_preferences_res, options.get_next_argument('properties', validation: Hash))
725
830
  return Main.result_status('modified')
@@ -732,26 +837,24 @@ module Aspera
732
837
  case options.get_next_command(%i[list show short_link])
733
838
  when :list
734
839
  default_query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
735
- default_query['workspace_id'] = aoc_api.workspace[:id] unless aoc_api.workspace[:id].eql?(:undefined)
840
+ workspace_id_hash(hash: default_query, string: true)
736
841
  return result_list('dropbox_memberships', fields: %w[dropbox_id dropbox.name], default_query: default_query)
737
842
  when :show
738
- return {type: :single_object, data: aoc_api.read(get_resource_path_from_args('dropboxes'))}
843
+ return Main.result_single_object(aoc_api.read(get_resource_path_from_args('dropboxes')))
739
844
  when :short_link
740
845
  return short_link_command(
741
- {
742
- workspace_id: aoc_api.workspace[:id],
743
- dropbox_id: get_resource_id_from_args('dropboxes'),
744
- name: ''
745
- },
746
- purpose_public: 'send_package_to_dropbox')
846
+ purpose_public: 'send_package_to_dropbox',
847
+ dropbox_id: get_resource_id_from_args('dropboxes'),
848
+ name: '',
849
+ **workspace_id_hash
850
+ )
747
851
  end
748
852
  when :send
749
853
  package_data = value_create_modify(command: package_command)
750
854
  new_user_option = options.get_option(:new_user_option)
751
855
  option_validate = options.get_option(:validate_metadata)
752
- # works for both normal usr auth and link auth
753
- package_data['workspace_id'] ||= aoc_api.workspace[:id]
754
-
856
+ # works for both normal user auth and link auth
857
+ workspace_id_hash(hash: package_data, string: true) unless package_data.key?('workspace_id')
755
858
  if !aoc_api.public_link.nil?
756
859
  aoc_api.assert_public_link_types(%w[send_package_to_user send_package_to_dropbox])
757
860
  box_type = aoc_api.public_link['purpose'].split('_').last
@@ -764,64 +867,49 @@ module Aspera
764
867
  created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
765
868
  Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
766
869
  # return all info on package (especially package id)
767
- return { type: :single_object, data: created_package[:info]}
870
+ return Main.result_single_object(created_package[:info])
768
871
  when :receive
769
872
  ids_to_download = nil
770
873
  if !aoc_api.public_link.nil?
771
874
  aoc_api.assert_public_link_types(['view_received_package'])
772
- # set the package id, it will
875
+ # set the package id from link
773
876
  ids_to_download = aoc_api.public_link['data']['package_id']
774
877
  end
775
878
  # get from command line unless it was a public link
776
879
  ids_to_download ||= instance_identifier
777
- skip_ids_data = []
778
- skip_ids_persistency = nil
779
- if options.get_option(:once_only, mandatory: true)
780
- # TODO: add query info to id
781
- skip_ids_persistency = PersistencyActionOnce.new(
782
- manager: persistency,
783
- data: skip_ids_data,
784
- id: IdGenerator.from_list(
785
- ['aoc_recv',
786
- options.get_option(:url, mandatory: true),
787
- aoc_api.workspace[:id]
788
- ].concat(aoc_api.additional_persistence_ids)))
789
- end
880
+ skip_ids_persistency = package_persistency
790
881
  case ids_to_download
791
882
  when SpecialValues::ALL, SpecialValues::INIT
792
- query = query_read_delete(default: PACKAGE_RECEIVED_BASE_QUERY)
793
- Aspera.assert_type(query, Hash){'query'}
794
- resolve_dropbox_name_default_ws_id(query)
795
- # remove from list the ones already downloaded
796
- all_ids = api_read_all('packages', query)[:data].map{|e|e['id']}
883
+ all_packages = list_all_packages_with_query[:items]
797
884
  if ids_to_download.eql?(SpecialValues::INIT)
798
- Aspera.assert(skip_ids_persistency){'Only with option once_only'}
799
- skip_ids_persistency.data.clear.concat(all_ids)
885
+ Aspera.assert(skip_ids_persistency){'INIT requires option once_only'}
886
+ skip_ids_persistency.data.clear.concat(all_packages.map{ |e| e['id']})
800
887
  skip_ids_persistency.save
801
888
  return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
802
889
  end
803
- # array here
804
- ids_to_download = all_ids.reject{|id|skip_ids_data.include?(id)}
890
+ # remove from list the ones already downloaded
891
+ reject_packages_from_persistency(all_packages, skip_ids_persistency)
892
+ ids_to_download = all_packages.map{ |e| e['id']}
805
893
  else
894
+ # single id to array
806
895
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
807
896
  end
808
897
  file_list =
809
898
  begin
810
- transfer.source_list.map{|i|{'source'=>i}}
899
+ transfer.source_list.map{ |i| {'source'=>i}}
811
900
  rescue Cli::BadArgument
812
901
  [{'source' => '.'}]
813
902
  end
814
903
  # list here
815
904
  result_transfer = []
816
- formatter.display_status("found #{ids_to_download.length} package(s).")
905
+ formatter.display_status("Found #{ids_to_download.length} package(s).")
817
906
  ids_to_download.each do |package_id|
818
907
  package_info = aoc_api.read("packages/#{package_id}")
819
908
  formatter.display_status("downloading package: [#{package_info['id']}] #{package_info['name']}")
820
909
  package_node_api = aoc_api.node_api_from(
821
910
  node_id: package_info['node_id'],
822
- workspace_id: aoc_api.workspace[:id],
823
- workspace_name: aoc_api.workspace[:name],
824
- package_info: package_info)
911
+ package_info: package_info,
912
+ **workspace_id_hash(name: true))
825
913
  statuses = transfer.start(
826
914
  package_node_api.transfer_spec_gen4(
827
915
  package_info['contents_file_id'],
@@ -830,22 +918,23 @@ module Aspera
830
918
  rest_token: package_node_api)
831
919
  result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
832
920
  # update skip list only if all transfer sessions completed
833
- if TransferAgent.session_status(statuses).eql?(:success)
834
- skip_ids_data.push(package_id)
835
- skip_ids_persistency&.save
921
+ if skip_ids_persistency && TransferAgent.session_status(statuses).eql?(:success)
922
+ skip_ids_persistency.data.push(package_id)
923
+ skip_ids_persistency.save
836
924
  end
837
925
  end
838
926
  return Main.result_transfer_multiple(result_transfer)
839
927
  when :show
840
928
  package_id = instance_identifier
841
929
  package_info = aoc_api.read("packages/#{package_id}")
842
- return { type: :single_object, data: package_info }
930
+ return Main.result_single_object(package_info)
843
931
  when :list
844
- display_fields = %w[id name bytes_transferred]
845
- display_fields.push('workspace_id') if aoc_api.workspace[:id].eql?(:undefined)
846
- return result_list('packages', fields: display_fields, base_query: PACKAGE_RECEIVED_BASE_QUERY) do |query|
847
- resolve_dropbox_name_default_ws_id(query)
848
- end
932
+ result = list_all_packages_with_query
933
+ skip_ids_persistency = package_persistency
934
+ reject_packages_from_persistency(result[:items], skip_ids_persistency)
935
+ display_fields = PACKAGE_LIST_DEFAULT_FIELDS
936
+ display_fields += ['workspace_id'] if aoc_api.workspace[:id].nil?
937
+ return Main.result_object_list(result[:items], fields: display_fields, total: result[:total])
849
938
  when :delete
850
939
  return do_bulk_operation(command: package_command, descr: 'identifier', values: instance_identifier) do |id|
851
940
  Aspera.assert_values(id.class, [String, Integer]){'identifier'}
@@ -864,16 +953,15 @@ module Aspera
864
953
  when :short_link
865
954
  folder_dest = options.get_next_argument('path', validation: String)
866
955
  home_node_api = aoc_api.node_api_from(
867
- node_id: aoc_api.home[:node_id],
868
- workspace_id: aoc_api.workspace[:id],
869
- workspace_name: aoc_api.workspace[:name])
956
+ node_id: aoc_api.home[:node_id],
957
+ **workspace_id_hash(name: true))
870
958
  shared_apfid = home_node_api.resolve_api_fid(aoc_api.home[:file_id], folder_dest)
871
959
  return short_link_command(
872
- {
873
- workspace_id: aoc_api.workspace[:id],
874
- node_id: shared_apfid[:api].app_info[:node_info]['id'],
875
- file_id: shared_apfid[:file_id]
876
- }, purpose_public: 'view_shared_file') do |resource_id|
960
+ purpose_public: 'view_shared_file',
961
+ node_id: shared_apfid[:api].app_info[:node_info]['id'],
962
+ file_id: shared_apfid[:file_id],
963
+ **workspace_id_hash
964
+ ) do |resource_id|
877
965
  # TODO: merge with node permissions ?
878
966
  # TODO: access level as arg
879
967
  access_levels = Api::Node::ACCESS_LEVELS # ['delete','list','mkdir','preview','read','rename','write']
@@ -885,13 +973,12 @@ module Aspera
885
973
  'tags' => {
886
974
  # TODO: really just here ? not in tags.aspera.files.workspace ?
887
975
  'url_token' => true,
888
- 'workspace_id' => aoc_api.workspace[:id],
889
- 'workspace_name' => aoc_api.workspace[:name],
890
976
  'folder_name' => File.basename(folder_dest),
891
977
  'created_by_name' => aoc_api.current_user_info['name'],
892
978
  'created_by_email' => aoc_api.current_user_info['email'],
893
979
  'access_key' => shared_apfid[:api].app_info[:node_info]['access_key'],
894
- 'node' => shared_apfid[:api].app_info[:node_info]['host']
980
+ 'node' => shared_apfid[:api].app_info[:node_info]['host'],
981
+ **workspace_id_hash(string: true, name: true)
895
982
  }
896
983
  }
897
984
  created_data = shared_apfid[:api].create('permissions', perm_data)
@@ -914,7 +1001,7 @@ module Aspera
914
1001
  when :launch
915
1002
  wf_id = instance_identifier
916
1003
  data = automation_api.create("workflows/#{wf_id}/launch", {})
917
- return {type: :single_object, data: data}
1004
+ return Main.result_single_object(data)
918
1005
  when :action
919
1006
  # TODO: not complete
920
1007
  wf_id = instance_identifier
@@ -925,16 +1012,17 @@ module Aspera
925
1012
  action = automation_api.create('actions', {'step_id' => step['id'], 'type' => 'manual'})
926
1013
  automation_api.update("steps/#{step['id']}", {'action_order' => [action['id']]})
927
1014
  wf = automation_api.read("workflows/#{wf_id}")
928
- return {type: :single_object, data: wf}
1015
+ return Main.result_single_object(wf)
929
1016
  end
930
1017
  end
931
1018
  when :admin
932
1019
  return execute_admin_action
933
1020
  when :gateway
934
1021
  require 'aspera/faspex_gw'
935
- url = value_create_modify(command: command, type: String)
936
- uri = URI.parse(url)
937
- server = WebServerSimple.new(uri)
1022
+ parameters = value_create_modify(command: command, default: {}).symbolize_keys
1023
+ uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
1024
+ server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
1025
+ Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?)
938
1026
  aoc_api.context = :files
939
1027
  server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.workspace[:id])
940
1028
  server.start